package il.adscan.scan.impl;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import il.adscan.domain.Advertisement;
import il.adscan.domain.AdvertisementFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

public abstract class BaseScanner implements AdScanner {

    static {
        LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
    }

    private List<Advertisement> advertisementList = new ArrayList<>();
    protected int pageCounter = 0; // Page from which parsing starting
    protected int lastPageToScan = -1; // End of parsing pages range
    protected int numberOfItemsToScan = -1;
    protected AdvertisementFilter filter;
    protected final WebClient webClient;


    protected BaseScanner() {
        this.webClient = getWebClient();
    }

    private WebClient getWebClient() {
        WebClient client = new WebClient(BrowserVersion.CHROME);
        client.getOptions().setJavaScriptEnabled(true);
        client.getOptions().setThrowExceptionOnScriptError(false);
        client.getOptions().setThrowExceptionOnFailingStatusCode(false);
        return client;
    }

    @Override
    public List<Advertisement> scan(AdvertisementFilter filter) throws IOException {
        try {
            this.filter = filter;
            HtmlPage htmlPage = getHtmlPageWithAppliedFilters(filter);
            numberOfItemsToScan = filter.getNumberOfItemsToScan();
            pageCounter = filter.getFirstPageToScan();
            lastPageToScan = filter.getLastPageToScan();
            scrapAllData(getPage(pageCounter, htmlPage.getUrl().toString()));
            return advertisementList;
        } finally {
            webClient.closeAllWindows();
        }
    }

    protected HtmlPage getHtmlPageWithAppliedFilters(AdvertisementFilter filter) throws IOException {
        HtmlPage htmlPage = getHTMLPage(getSiteURL());
        htmlPage = applyFilters(htmlPage, filter);
        return htmlPage;
    }

    private void scrapAllData(HtmlPage htmlPage) throws IOException {
        if (!isProcessCurrentPage(htmlPage)) {
            return;
        }
        advertisementList.addAll(scrapPageData(htmlPage));
        pageCounter++;
        if (hasNextPage(htmlPage) && advertisementList.size() < numberOfItemsToScan && (pageCounter <= lastPageToScan || lastPageToScan == -1)) {
            scrapAllData(getNextPage(htmlPage));
        }
    }

    protected List<Advertisement> scrapPageData(HtmlPage htmlPage) throws IOException {
        Document doc = Jsoup.parse(htmlPage.asXml());
        Elements elements = getElements(doc);
        List<Advertisement> advertisementList = new ArrayList<>();
        int i = 0;
        ExecutorService executorService = Executors.newFixedThreadPool(20);

        Set<Callable<Advertisement>> callables = new HashSet<>();
        List<Future<Advertisement>> advertisementFutureList = new ArrayList<>();
        for (Element element : elements) {
            if (advertisementList.size() >= numberOfItemsToScan && numberOfItemsToScan != -1) {
                break;
            }
            callables.add(getAdScraper(element));
            i++;
        }
        try {
            advertisementFutureList = executorService.invokeAll(callables);
            for (Future<Advertisement> advertisementFuture : advertisementFutureList) {
                advertisementList.add(advertisementFuture.get());
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        return advertisementList;
    }

    protected HtmlPage getNextPage(HtmlPage htmlPage) throws IOException {
        return getPage(pageCounter, htmlPage.getUrl().toString());
    }

    protected abstract HtmlPage getPage(int pageNumber, String baseURI) throws IOException;

    protected abstract boolean hasNextPage(HtmlPage htmlPage);

    protected abstract boolean isProcessCurrentPage(HtmlPage htmlPage);

    protected abstract HtmlPage applyFilters(HtmlPage htmlPage, AdvertisementFilter filter);

    protected abstract boolean isAvailable(Element element);

    protected abstract Advertisement getAdvertisementFromLink(String link) throws IOException;

    protected abstract String getOriginalIdFromLink(String link);

    protected abstract AdScraper getAdScraper(Element element);

    protected abstract Elements getElements(Document doc);

    protected HtmlPage getHTMLPage(String site) throws IOException {
        HtmlPage page = webClient.getPage(site);
        return page;
    }

    protected String getParametersDelimiter(String baseURI) {
        return StringUtils.contains(baseURI, "?") ? "&" : "?";
    }

    protected void waitForScript(int time) {
        webClient.waitForBackgroundJavaScript(time);
    }

    public List<Advertisement> getAdvertisementList() {
        return advertisementList;
    }

    protected Document connect(String site) throws IOException {
        return Jsoup.connect(site).timeout(20000).get();
    }

    protected String getElementValue(Element element) {
        return element.child(0).html();
    }

    protected HtmlPage getNewHTMLPage(String link) throws IOException {
        return getWebClient().getPage(link);
    }

}
